package com.siwei.infoextract.rule;

import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.siwei.infoextract.wrapper.parser.DomNode;
import com.siwei.infoextract.wrapper.parser.DomNodeIteratorCallback;
import com.siwei.infoextract.wrapper.parser.DomTreeTraversal;

public class RuleEngine {
	public static final String ADDRESS_RULE_ENGINE_NAME = "ADDRESS";
	public static final String PHONE_RULE_ENGINE_NAME = "PHONE";
	public static final String TITLE_RULE_ENGINE_NAME = "TITLE";

	Pattern rulePattern = Pattern.compile("IF(.*?)THEN(.*?)\\[([\\d]+?) ([\\d]+?)\\]", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); 
	Pattern defaultRulePattern = Pattern.compile("\\(DEFAULT\\)(.*?)\\[([\\d]+?) ([\\d]+?)\\]", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
	
	List<Rule> rules = new ArrayList<Rule>();
	Rule defaultRule;
	String ruleFile;
	String name;

	public RuleEngine(String ruleFile, String name) {
		this.ruleFile = ruleFile;
		this.name = name;
	}

	public List<Rule> getRules() {
		return rules;
	}

	public Rule getDefaultRule() {
		return defaultRule;
	}

	public String getRuleFile() {
		return ruleFile;
	}
	
	public String getName() {
		return this.name;
	}

	public void load() throws IOException {
		StringBuffer strBuffer = new StringBuffer();
		FileReader fileReader = null; 
		try {
			fileReader = new FileReader(this.ruleFile);

			int c;
			while((c = fileReader.read()) != -1) {
				strBuffer.append((char)c);
			}
		} finally {
			try {
				if (fileReader != null) {
					fileReader.close();
				}
			} catch (IOException ioe) {
				// skip the exception here 
			}
		}

		Matcher matcher = rulePattern.matcher(strBuffer);
		while(matcher.find()) {
			parseCommonRule(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4));
		}

		matcher = defaultRulePattern.matcher(strBuffer);
		if (matcher.find()) {
			parseDefaultRule(matcher.group(1), matcher.group(2), matcher.group(3));
		}
	}

	/**
	 * Run rules on the dom node
	 * @param domNode
	 */
	public void apply(DomNode domNode) {
		// TODO Auto-generated method stub
		DomTreeTraversal traversal = new DomTreeTraversal(domNode);

		final String finalizedName = this.name;
		final Rule finalizedDefaultRule = this.defaultRule;

		traversal.addCallback(new DomNodeIteratorCallback(){
			public void call(DomNode domNode) {
				
				boolean matched = false;
				for (Iterator<Rule> iter = rules.iterator(); iter.hasNext();) {
					Rule rule = iter.next();
					matched = rule.apply(domNode);
					if (matched) {					
						Map<String, String> map;
						if (domNode.getFlag() == null) {
							map = new HashMap<String, String>();
							domNode.setFlag(map);
						} else {
							map = (Map<String, String>)domNode.getFlag();
						}
						map.put(finalizedName, (String)rule.getPredict());

						break;
					}
				}

				// if all rule have not been matched, we will appy default rule
				if (!matched) {
					Map<String, String> map;
					if (domNode.getFlag() == null) {
						map = new HashMap<String, String>(); 
					} else {
						map = (Map<String, String>)domNode.getFlag();
					}
					map.put(finalizedName, (String)finalizedDefaultRule.getPredict());
				}
			}
		});
		traversal.traverse();
	}

	/**
	 * Parse common rule
	 * e.g. IF    isAddressPattern = true THEN  class = true  [8 0]
	 * @param condition
	 * @param predict
	 * @param positiveNum
	 * @param negativeNum
	 */
	public void parseCommonRule(String condition, String predict, String positiveNum,
			String negativeNum) {
				Rule rule = new Rule(); 
				
				// TODO Auto-generated method stub
				String[] criterias =  condition.split("AND");
				String[] parts = null;
				String criteriaName = null;
				String criteriaValue = null;
				
				List<Criteria> parsedCriterias = new ArrayList<Criteria>();

				for (int i = 0; i < criterias.length; i++) {
					 parts =  criterias[i].split("=");
					 
					 if (parts.length != 2) {
						 // This is a invalid criteria
						 continue;
					 }
					 criteriaName = parts[0].trim();
					 criteriaValue = parts[1].trim();
			
					 parsedCriterias.add(Criteria.createCriteria(criteriaName, criteriaValue));
				}
				rule.setCriterias(parsedCriterias);
				
				parts = predict.split("=");
				if (parts.length != 2) {
					throw new RuntimeException("Rule is of invalid format:" + predict);
				}
				rule.setPredict(parts[1].trim());

				rule.setPositiveNum(Integer.parseInt(positiveNum.trim()));
				rule.setNegativeNum(Integer.parseInt(negativeNum.trim()));
			
				rules.add(rule);
			}

	/**
	 * Parse default rule
	 * e.g. (DEFAULT) class = false  [10 572]
	 * @param predict
	 * @param positiveNum
	 * @param negativeNum
	 */
	public void parseDefaultRule(String predict, String positiveNum, String negativeNum) {
		// TODO Auto-generated method stub
		defaultRule = new Rule();
		
		String[] parts = predict.split("=");
		
		if (parts.length != 2) {
			throw new RuntimeException("Rule is of invalid format:" + predict);
		}
	
		defaultRule.setPredict(parts[1].trim());
		defaultRule.setPositiveNum(Integer.parseInt(positiveNum.trim()));
		defaultRule.setNegativeNum(Integer.parseInt(negativeNum.trim()));
	}
}
